package com.atguigu.spzx.product.service.impl;

import com.atguigu.spzx.product.service.TestService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class TestServiceImpl implements TestService {

    private static ScheduledExecutorService pool = Executors.newScheduledThreadPool(8);

    @Autowired
    RedisTemplate<String, String> redisTemplate;


    @Autowired
    RedissonClient redissonClient;


    public void testLock() {
        RLock lock = redissonClient.getLock("lock"); //获取锁对象
        boolean tryLock = lock.tryLock(); //尝试加锁
        if (tryLock) { //加锁成功，执行业务
            try {
                //执行业务逻辑

                //1.从Redis缓存中获取key="num"的值
                String value = redisTemplate.opsForValue().get("num");
                if (StringUtils.isEmpty(value)) {
                    redisTemplate.opsForValue().set("num", "1");
                }

                //2.对获取到值进行+1操作
                int num = Integer.parseInt(value);
                redisTemplate.opsForValue().set("num", String.valueOf(++num));

            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                //解锁
                lock.unlock();
            }
        } else { //加锁失败，自旋，等待再抢
            try {
                TimeUnit.MILLISECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            testLock();
        }
    }


    //synchronized 本地锁，只对当前JVM对象锁定生效。集群情况失效。

    //使用分布式锁解决集群情况下锁的使用。全局锁。也叫分布式锁。
    /*@Override
    public  void testLock() {
//        ##### **常见问题2**
//
//        问题说明：释放锁之前服务器宕机了，锁没有释放，导致出现死锁现象，怎么解决?
//
//                解决方案：给锁设置一个过期时间，让锁具备自动释放的功能、

//        ##### **常见问题3**
//
//        问题说明：加锁完毕以后，在设置过期时间之前服务器宕机了，锁不能被释放，导致出现死锁现象，怎么解决?
//
//                解决方案：保证加锁和给锁设置过期时间的原子性

//        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "1"); //通过redis实现分布式锁。
//        redisTemplate.expire("lock",30, TimeUnit.SECONDS);

        //Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "1",30,TimeUnit.SECONDS); //通过redis实现分布式锁。


//        ##### **常见问题4**
//
//        问题说明：在释放锁的时候把别的线程上的锁给释放了，怎么解决?
        String lockValue = UUID.randomUUID().toString().replace("-", "");

        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", lockValue,30,TimeUnit.SECONDS); //通过redis实现分布式锁。

//        ##### **常见问题6**
//
//        问题说明：业务执行的时间大于锁的过期时间，导致其他的线程在锁自动释放完毕以后加锁成功，分布式锁失效。
//
//        解决方案：提供续期机制(看门狗机制)
//
//        常见手段：创建一个定时任务，每隔200ms给锁续期到初始过期时间

        pool.scheduleAtFixedRate(()->redisTemplate.expire("lock",30, TimeUnit.SECONDS),200,200,TimeUnit.MILLISECONDS);


        if(lock){ //加锁成功，获取了分布式锁。
            try {
                //执行业务逻辑

                //1.从Redis缓存中获取key="num"的值
                String value = redisTemplate.opsForValue().get("num");
                if (StringUtils.isEmpty(value)) {
                    redisTemplate.opsForValue().set("num" , "1");
                }

                //2.对获取到值进行+1操作
                int num = Integer.parseInt(value);
                redisTemplate.opsForValue().set("num", String.valueOf(++num));

            } catch (Exception e) {
                e.printStackTrace();
            } finally {
//                ##### **常见问题1**
//
//                问题说明：业务异常锁没有释放，导致出现死锁现象，怎么解决?
//
//                        解决方案：将释放锁的代码写到finally语句块中
                //释放锁
                //redisTemplate.delete("lock");


//                String lockredis = redisTemplate.opsForValue().get("lock");
//                if(lockValue.equals(lockredis)){
//                    redisTemplate.delete("lock");
//                }

//                ##### **常见问题5**
//
//                问题说明：判断锁的操作和释放锁的操作，被其他线程加锁线程所中断，导致释放的是别的线程所对应的锁，怎么解决?
//                解决方案：使用lua脚本保证判断和释放锁操作的原子性

                String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
                        "then\n" +
                        "    return redis.call(\"del\",KEYS[1])\n" +
                        "else\n" +
                        "    return 0\n" +
                        "end";
                Long result = redisTemplate.execute(new DefaultRedisScript<>(script,Long.class), Arrays.asList("lock"), lockValue);
                System.out.println("result = " + result);
                    // 判断执行结果
                if (result == 1) {
                    log.info("锁是自己的，删除锁成功...");
                }else {
                    log.error("锁是别人的，删除锁失败...");
                }
            }
        }else{
            //自旋  等待-抢锁。
            try {
                Thread.sleep(30);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            testLock();
        }


    }*/
}
